﻿using System;
using System.IO;
using System.Text;
using System.Diagnostics;
using Csu.Modsim.ModsimModel;
using Csu.Modsim.ModsimIO;
using ASquared.ModelOptimization;
using ASquared; 

namespace iwpm.optimization
{
    public class GrandCouleeTestBusWind : IScenarioSolvable
    {
        public static string[] Delimiter = new string[] { "\t" }; 
        private Model _model;
        private StreamWriter _sw = null;
        private HydropowerTarget[] _left, _right;
        private TwoBusSystem _grid;
        private OptiModel _optiModel;
        private ConditionalGradientSolver _solver;
        private double _tolerance = 50;
        private int _maxIterations = 200;
//        private int _tolerance = 10, _maxIterations = 100;
        private bool _errorOccurred = false;
        private string _name, _xyfile, _newxyfile, _powDevsFile;
        private double _hydroWeight, _maxPowerFlow, _scaleFactor; 
        private Stopwatch _sWatch = new Stopwatch();
        private int _iter = -1; 

        public bool WriteLogFile = true;

        public double Tolerance
        {
            get
            {
                return _tolerance;
            }
            set
            {
                _tolerance = value; 
            }
        }

        public int MaxIterations
        {
            get
            {
                return _maxIterations;
            }
            set
            {
                _maxIterations = value; 
            }
        }

        public string Name
        {
            get
            {
                return _name;
            }
        }

        public bool ErrorOccurred
        {
            get
            {
                return _errorOccurred;
            }
        }

        public TimeSpan SolutionTime
        {
            get
            {
                return _sWatch.Elapsed; 
            }
        }

        public GrandCouleeTestBusWind(string name, string xyfile, string newxyfile, string powDevsFile, double hydroWeight, double maxPowerFlow, double scaleFactor)
        {
            _name = name;
            _xyfile = xyfile;
            _newxyfile = newxyfile;
            _powDevsFile = powDevsFile;
            _hydroWeight = hydroWeight;
            _maxPowerFlow = maxPowerFlow;
            _scaleFactor = scaleFactor;
        }

        private void Setup()
        {
            // Read model 
            _model = new Model();
            _model.OnMessage += new Model.OnMessageDelegate(_model_OnMessage);
            _model.OnModsimError += new Model.OnModsimErrorDelegate(_model_OnModsimError);
            XYFileReader.Read(_model, _xyfile);

            // Write the model file 
            string dir = Path.GetDirectoryName(Path.GetFullPath(_newxyfile)) + "\\";
            if (!Directory.Exists(dir))
            {
                Console.WriteLine("Attempting to create directory: " + dir);
                Directory.CreateDirectory(dir);
            }
            XYFileWriter.Write(_model, _newxyfile, XYFileWriter.CompressionLevelTypes.Full, OutputVersionType.V8_3_0);

            // Read the new model file 
            _model = new Model();
            _model.OnMessage += new Model.OnMessageDelegate(_model_OnMessage);
            _model.OnModsimError += new Model.OnModsimErrorDelegate(_model_OnModsimError);
            XYFileReader.Read(_model, _newxyfile);

            // left and right generators (separated by one branch)
            _left = new HydropowerTarget[] { _model.hydro.GetHydroTarget("CHJ_Target") };
            _right = new HydropowerTarget[] { _model.hydro.GetHydroTarget("GCL_GenTarget"), _model.hydro.GetHydroTarget("BNK_PumpTarget"), _model.hydro.GetHydroTarget("BNK_GenTarget") };

            // model options
            _model.IncludeSpillLinks = false;
            _model.hydro.IterativeTechnique = IterativeSolutionTechnique.Nothing; // SuccApprox; //.Nothing; //.SeparateHydroLinks; // 
            // _model.hydro.SetHydroLinkCosts(_hydroWeight);

            // two-bus system inputs 
            //HydropowerController.ElevType = ElevationFunctionType.Polynomial; 
            TimeSeries powDevs = HydroTargetUpdater.ReadPowerFile(_powDevsFile, _model);
            powDevs.Scale(_scaleFactor); // multiply each element by a scaling factor
            // don't want to add wind power to targets here... done in power system modeling of TwoBusSystem
            //HydroTargetUpdater.AddToTargets(_model, powDevs); 
            TwoBusSystemInputs inputs = new TwoBusSystemInputs(_model, _left, _right, powDevs, null, _hydroWeight, _maxPowerFlow);

            // build OptiModel 
            _optiModel = new OptiModel(_model, ObjectiveFormulation.TwoBusSystem, inputs);
            _grid = (TwoBusSystem)_optiModel.objective;
            _grid.UseAugmentedLagrangian = true;
            _grid.MinimizeSwing = true;
            if (this.WriteLogFile)
                _grid.PowerLogFile = dir + Path.GetFileNameWithoutExtension(_model.fname) + "_log_power.txt";
            _grid.PowerMaxIterations = _maxIterations;
            _grid.PowerTolerance = _tolerance; 
            _model.maxit = _maxIterations * 2;
            
            // build solver
            _solver = new ConditionalGradientSolver(_optiModel);
#if DEBUG
            _solver.LogFile = dir + Path.GetFileNameWithoutExtension(_model.fname) + "_log_cgm.txt";
#endif
            _solver.LineSearchToleranceUpdateFactor = 1.0;
            // not using unconstrained problem solution anymore 
            // _solver.IterationToRunUnconstrained = 30;
            _solver.IterBottom += new EventHandler(_solver_IterBottom);
            _solver.Converged += new EventHandler(_solver_Converged);
            _solver.Tolerance = _tolerance;
            _solver.MaxIterations = _maxIterations;
            
        }

        public void Solve()
        {
            _errorOccurred = false;
            try
            {
                Setup();

                _sWatch.Reset();
                _sWatch.Start();
                _solver.Solve();
                _sWatch.Stop();
                _sw.Close();
            }
            catch (Exception ex)
            {
                _model.FireOnError(ex.ToString());
            }
            finally
            {
                _sw = null;
            }
        }

        void _solver_IterBottom(object sender, EventArgs e)
        {
            _iter++;
            if (_iter == 0)
                return;

            // update Lagrange multipliers
            bool convg = _grid.UpdateMultipliers(); 

            // check convergence
            if (!convg)
                _solver.IsConverged = false;
        }

        void _solver_Converged(object sender, EventArgs e)
        {
            _iter = 0;
            writeLog();
        }

        private void _model_OnMessage(string message)
        {
            Console.WriteLine(message);
        }

        private void _model_OnModsimError(string message)
        {
            _errorOccurred = true;
            Console.WriteLine(message); 
        }

        private void writeLog()
        {
            if (!WriteLogFile) 
                return; 

            if (_sw == null)
            {
                try
                {
                    string dir = Path.GetDirectoryName(Path.GetFullPath(_model.fname)) + @"\"; 
                    string basename = dir + Path.GetFileNameWithoutExtension(_model.fname);
                    _sw = new StreamWriter(basename + "_power.csv");
                    _optiModel.Debug(basename + "_debug.csv");
                    _sw.WriteLine("TimeStep," 
                        + string.Join(",", Array.ConvertAll(_left, 
                            targ => targ.Name + " Energy Production (MWh)," 
                                  + targ.Name + " Energy Target (MWh)")) + "," 
                        + string.Join(",", Array.ConvertAll(_right, 
                            targ => targ.Name + " Energy Production (MWh)," 
                                  + targ.Name + " Energy Target (MWh)"))
                        + ",Left Bus Deviation (MWh),Right Bus Deviation (MWh),Swing Bus Power (MW),Max Power Flow (MW),Calculated Power Flow (MW),Total Load(MW),Slack Variable Left (MWh),Slack Variable Right (MWh)");
                }
                catch (Exception ex)
                {
                    WriteLogFile = false;
                    Console.WriteLine("Exception occurred when attempting to open output power file. Is it open in another application?\n" + ex.ToString());
                    return; 
                }
            }
            StringBuilder s = new StringBuilder(_model.mInfo.CurrentModelTimeStepIndex.ToString() + ",");
            s.Append(string.Join(",", Array.ConvertAll(_left, targ => targ.Energy.ToString() + "," + targ.EnergyTarget.ToString())));
            s.Append(",");
            s.Append(string.Join(",", Array.ConvertAll(_right, targ => targ.Energy.ToString() + "," + targ.EnergyTarget.ToString())));
            s.Append("," + _grid.LeftPowerDeviation.ToString());
            s.Append("," + _grid.RightPowerDeviation.ToString());
            s.Append("," + _grid.SwingBusPower.ToString());
            s.Append("," + _grid.MaxPowerFlow.ToString());
            s.Append("," + _grid.CalculatedPowerFlow.ToString());
            s.Append("," + _grid.TotalLoad.ToString());
            s.Append("," + _grid.SlackLeft.ToString());
            s.Append("," + _grid.SlackRight.ToString());
            _sw.WriteLine(s.ToString());
            _sw.Flush();
        }

        public static GrandCouleeTestBusWind FromString(string s)
        {
            string[] array = s.Split(Delimiter, StringSplitOptions.None);
            if (array.Length < 7) 
                return null; 

            double hydroWeight, maxPowerFlow, scaleFactor;
            if (double.TryParse(array[4], out hydroWeight)
                && double.TryParse(array[5], out maxPowerFlow)
                && double.TryParse(array[6], out scaleFactor))
            {
                return new GrandCouleeTestBusWind(array[0], array[1], array[2], array[3],
                    hydroWeight, maxPowerFlow, scaleFactor);
            }
            else
            {
                return null;
            }
        }

        public override string ToString()
        {
            string[] s = new string[] { _name, _xyfile, _newxyfile, _powDevsFile, 
                _hydroWeight.ToString(), _maxPowerFlow.ToString() , _scaleFactor.ToString()};
            return string.Join(Delimiter[0], s); 
        }
    }
}
